#include "sincity/sc_nettransport.h"
#include "sincity/sc_debug.h"

#include "tinymedia/tmedia_defaults.h"

#include "tnet_transport.h"
#include "tnet_utils.h"

#include "tsk_buffer.h"
#include "tsk_memory.h"
#include "tsk_base64.h"

#include <assert.h>

#if !defined(kSCMaxStreamBufferSize)
#	define kSCMaxStreamBufferSize 0xFFFF
#endif

//
//	SCNetPeer
//

SCNetPeer::SCNetPeer(SCNetFd nFd, bool bConnected /*= false*/, const void* pcData /*= NULL*/, size_t nDataSize /*= 0*/)
{
    m_bConnected = bConnected;
    m_nFd = nFd;
    m_pWrappedBuffer = tsk_buffer_create(pcData, nDataSize);
    m_bRawContent = false;
    m_pWsKey = tsk_null;
}

SCNetPeer::~SCNetPeer()
{
    TSK_OBJECT_SAFE_FREE(m_pWrappedBuffer);
    TSK_FREE(m_pWsKey);
}

const void* SCNetPeer::getDataPtr()
{
    return m_pWrappedBuffer ? m_pWrappedBuffer->data : NULL;
}

size_t SCNetPeer::getDataSize()
{
    return m_pWrappedBuffer ? m_pWrappedBuffer->size : 0;
}

#if 0
// IMPORTANT: data sent using this function will never be encrypted
bool SCNetPeer::sendData(const void* pcDataPtr, size_t nDataSize)
{
    if (!pcDataPtr || !nDataSize) {
        SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Invalid parameter");
        return false;
    }
    return (tnet_sockfd_send(getFd(), pcDataPtr, nDataSize, 0) == nDataSize);
}
#endif

bool SCNetPeer::buildWsKey()
{
    char WsKey[30];
    const int count = sizeof(WsKey)/sizeof(WsKey[0]);
    for (int i = 0; i < count - 1; ++i) {
        WsKey[i] = rand() % 0xFF;
    }
    WsKey[count - 1] = '\0';
    
    return tsk_base64_encode((const uint8_t*)WsKey, (count - 1), &m_pWsKey) > 0;
}


//
//	SCNetPeerStream
//

bool SCNetPeerStream::appenData(const void* pcData, size_t nDataSize)
{
    return m_pWrappedBuffer ? tsk_buffer_append(m_pWrappedBuffer, pcData, nDataSize) == 0 : false;
}

bool SCNetPeerStream::remoteData(size_t nPosition, size_t nSize)
{
    return m_pWrappedBuffer ? tsk_buffer_remove(m_pWrappedBuffer, nPosition, nSize) == 0 : false;
}

bool SCNetPeerStream::cleanupData()
{
    return m_pWrappedBuffer ? tsk_buffer_cleanup(m_pWrappedBuffer) == 0 : false;
}


//
//	SCNetTransport
//
SCNetTransport::SCNetTransport(SCNetTransporType_t eType, const char* pcLocalIP, unsigned short nLocalPort)
: m_bValid(false)
, m_bStarted(false)
{
    m_eType = eType;
    const char *pcDescription;
    tnet_socket_type_t eSocketType;
    bool bIsIPv6 = false;
    
    if(pcLocalIP && nLocalPort) {
        bIsIPv6 = (tnet_get_family(pcLocalIP, nLocalPort) == AF_INET6);
    }
    
    switch (eType) {
        case SCNetTransporType_TCP:
        case SCNetTransporType_WS: {
            pcDescription = bIsIPv6 ? "TCP/IPv6 transport" : "TCP/IPv4 transport";
            eSocketType = bIsIPv6 ? tnet_socket_type_tcp_ipv6 : tnet_socket_type_tcp_ipv4;
            break;
        }
        case SCNetTransporType_TLS:
        case SCNetTransporType_WSS: {
            pcDescription = bIsIPv6 ? "TLS/IPv6 transport" : "TLS/IPv4 transport";
            eSocketType = bIsIPv6 ? tnet_socket_type_tls_ipv6 : tnet_socket_type_tls_ipv4;
            break;
        }
        default: {
            SC_ASSERT(false);
            return;
        }
    }
    
    if ((m_pWrappedTransport = tnet_transport_create(pcLocalIP, nLocalPort, eSocketType, pcDescription))) {
        if (TNET_SOCKET_TYPE_IS_STREAM(eSocketType)) {
            tnet_transport_set_callback(m_pWrappedTransport, SCNetTransport::SCNetTransportCb_Stream, this);
        }
        else {
            SC_ASSERT(false);
            return;
        }
        
        // webproxy
        const char *webproxy_type = tsk_null, *webproxy_host = tsk_null, *webproxy_login = tsk_null, *webproxy_password = tsk_null;
        unsigned short webproxy_port = 0;
        SC_ASSERT(tnet_transport_set_proxy_auto_detect(m_pWrappedTransport, tmedia_defaults_get_webproxy_auto_detect()) == 0);
        SC_ASSERT(tmedia_defaults_get_webproxy_info(&webproxy_type, &webproxy_host, &webproxy_port, &webproxy_login, &webproxy_password) == 0);
        SC_ASSERT(tnet_transport_set_proxy_info(m_pWrappedTransport, tnet_proxy_type_from_string(webproxy_type), webproxy_host, webproxy_port, webproxy_login, webproxy_password) == 0);
    }
    
    m_oPeersMutex = new SCMutex();
    
    m_bValid = (m_oPeersMutex && m_pWrappedTransport);
}

SCNetTransport::~SCNetTransport()
{
    stop();
    TSK_OBJECT_SAFE_FREE(m_pWrappedTransport);
    SC_DEBUG_INFO("*** SCNetTransport destroyed ***");
}

bool SCNetTransport::setSSLCertificates(const char* pcPrivateKey, const char* pcPublicKey, const char* pcCA, bool bVerify /*= false*/)
{
    return (tnet_transport_tls_set_certs(m_pWrappedTransport, pcCA, pcPublicKey, pcPrivateKey, (bVerify ? tsk_true : tsk_false)) == 0);
}

bool SCNetTransport::start()
{
    m_bStarted = (tnet_transport_start(m_pWrappedTransport) == 0);
    return m_bStarted;
}

SCNetFd SCNetTransport::connectTo(const char* pcHost, unsigned short nPort)
{
    if (!pcHost || !nPort) {
        SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Invalid parameter");
        return (SCNetFd)TNET_INVALID_FD;
    }
    if (!isValid()) {
        SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Transport not valid");
        return (SCNetFd)TNET_INVALID_FD;
    }
    if(!isStarted()) {
        SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Transport not started");
        return (SCNetFd)TNET_INVALID_FD;
    }
    
    return tnet_transport_connectto_2(m_pWrappedTransport, pcHost, nPort);
}

bool SCNetTransport::isConnected(SCNetFd nFd)
{
    SCObjWrapper<SCNetPeer*> oPeer = getPeerByFd(nFd);
    return (oPeer && oPeer->isConnected());
}

bool SCNetTransport::sendData(SCNetFd nFdFrom, const void* pcDataPtr, size_t nDataSize)
{
    if (!pcDataPtr || !nDataSize || !SCNetFd_IsValid(nFdFrom)) {
        SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Invalid parameter");
        return false;
    }
    return (tnet_transport_send(m_pWrappedTransport, nFdFrom, pcDataPtr, nDataSize) == nDataSize);
}

bool SCNetTransport::sendData(SCObjWrapper<SCNetPeer*> oPeer, const void* pcDataPtr, size_t nDataSize)
{
    return sendData(oPeer->getFd(), pcDataPtr, nDataSize);
}

bool SCNetTransport::close(SCNetFd nFd)
{
    SCNetFd _nFd = nFd;
    bool ret = (tnet_transport_remove_socket(m_pWrappedTransport, &nFd) == 0);
#if SC_UNDER_IPHONE || SC_UNDER_IPHONE_SIMULATOR // FIXME: hack, on iOS no callback from Doubango network layer when socket is closed
    if(SCNetFd_IsValid(_nFd) && !SCNetFd_IsValid(nFd)) {
#if !defined(TSK_DEBUG_WARN)
#   define TSK_DEBUG_WARN SC_DEBUG_WARN
#endif
        TSK_RUNNABLE_ENQUEUE(m_pWrappedTransport, event_removed, ((tnet_transport_t*)m_pWrappedTransport)->callback_data, _nFd);
    }
#endif
    return ret;
}

bool SCNetTransport::stop()
{
    m_bStarted = false;
    return (tnet_transport_shutdown(m_pWrappedTransport) == 0);
}

bool SCNetTransport::hasPeer(SCNetFd nFd)
{
    m_oPeersMutex->lock();
    bool exists = (m_Peers.find(nFd) != m_Peers.end());
    m_oPeersMutex->unlock();
    return exists;
}

SCObjWrapper<SCNetPeer*> SCNetTransport::getPeerByFd(SCNetFd nFd)
{
    SCObjWrapper<SCNetPeer*> m_Peer = NULL;
    
    m_oPeersMutex->lock();
    std::map<SCNetFd, SCObjWrapper<SCNetPeer*> >::iterator iter = m_Peers.find(nFd);
    if (iter != m_Peers.end()) {
        m_Peer = iter->second;
    }
    m_oPeersMutex->unlock();
    
    return m_Peer;
}

void SCNetTransport::insertPeer(SCObjWrapper<SCNetPeer*> oPeer)
{
    if (oPeer) {
        m_oPeersMutex->lock();
        m_Peers.insert( std::pair<SCNetFd, SCObjWrapper<SCNetPeer*> >(oPeer->getFd(), oPeer) );
        m_oPeersMutex->unlock();
    }
}

void SCNetTransport::removePeer(SCNetFd nFd)
{
    std::map<SCNetFd, SCObjWrapper<SCNetPeer*> >::iterator iter;
    m_oPeersMutex->lock();
    if ((iter = m_Peers.find(nFd)) != m_Peers.end()) {
        m_Peers.erase(iter);
    }
    m_oPeersMutex->unlock();
}

int SCNetTransport::SCNetTransportCb_Stream(const tnet_transport_event_t* e)
{
    SCObjWrapper<SCNetPeer*> oPeer = NULL;
    SCNetTransport* This = (SCNetTransport*)e->callback_data;
    
    switch (e->type) {
        case event_error:
        case event_removed:
        case event_closed: {
            oPeer = (This)->getPeerByFd(e->local_fd);
            if (!oPeer) {
                // Peer doesn't exist because we never received "event_connected" or "event_accepted"
                oPeer = new SCNetPeerStream(e->local_fd, false);
            }
            if (oPeer) {
                oPeer->setConnected(false);
                (This)->removePeer(e->local_fd);
                if ((This)->m_oCallback) {
                    (This)->m_oCallback->onConnectionStateChanged(oPeer);
                }
            }
            break;
        }
        case event_connected:
        case event_accepted: {
            oPeer = (This)->getPeerByFd(e->local_fd);
            if (oPeer) {
                oPeer->setConnected(true);
            }
            else {
                oPeer = new SCNetPeerStream(e->local_fd, true);
                (This)->insertPeer(oPeer);
            }
            if ((This)->m_oCallback) {
                (This)->m_oCallback->onConnectionStateChanged(oPeer);
            }
            break;
        }
        
        case event_data: {
            oPeer = (This)->getPeerByFd(e->local_fd);
            if (!oPeer) {
                SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Data event but no peer found!");
                return -1;
            }
            
            size_t nConsumedBytes = oPeer->getDataSize();
            if ((nConsumedBytes + e->size) > kSCMaxStreamBufferSize) {
                SC_DEBUG_ERROR_EX(kSCMobuleNameNetTransport, "Stream buffer too large[%u > %u]. Did you forget to consume the bytes?", (unsigned)(nConsumedBytes + e->size), (unsigned)kSCMaxStreamBufferSize);
                dynamic_cast<SCNetPeerStream*>(*oPeer)->cleanupData();
            }
            else {
                if ((This)->m_oCallback) {
                    if (dynamic_cast<SCNetPeerStream*>(*oPeer)->appenData(e->data, e->size)) {
                        nConsumedBytes += e->size;
                    }
                    (This)->m_oCallback->onData(oPeer, nConsumedBytes);
                }
                if (nConsumedBytes) {
                    dynamic_cast<SCNetPeerStream*>(*oPeer)->remoteData(0, nConsumedBytes);
                }
            }
            break;
        }
        
        default: {
            break;
        }
    }
    
    return 0;
}